package com.sh.rbac.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sh.rbac.domain.constant.CacheConstants;
import com.sh.rbac.domain.constant.CommonConstants;
import com.sh.rbac.domain.converter.UserConverter;
import com.sh.rbac.domain.dto.user.*;
import com.sh.rbac.domain.entity.LoginUser;
import com.sh.rbac.domain.entity.User;
import com.sh.rbac.domain.entity.UserRole;
import com.sh.rbac.domain.enums.UserTypeEnum;
import com.sh.rbac.exception.CustomException;
import com.sh.rbac.mapper.RoleMapper;
import com.sh.rbac.mapper.UserMapper;
import com.sh.rbac.mapper.UserRoleMapper;
import com.sh.rbac.result.ResultCodeEnum;
import com.sh.rbac.service.ParamService;
import com.sh.rbac.service.UserService;
import com.sh.rbac.utils.JwtUtils;
import com.sh.rbac.utils.SecurityHolderUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author lucky
 * @description 针对表【sys_user(用户表)】的数据库操作Service实现
 * @createDate 2024-04-04 21:47:19
 */

@Transactional
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private ParamService paramService;


    @Override
    public Map<String, Object> login(LoginDTO loginDTO) {
        String key = loginDTO.getRandomStr();

        // 获取缓存中的验证码
        String code = (String) redisTemplate.opsForValue().get(CacheConstants.CAPTCHA_KEY + key);

        // 校验验证码
        if (!Objects.equals(code, loginDTO.getCode())) {
            throw new CustomException(ResultCodeEnum.CODE_ERROR);
        }

        try {
            // 构建用户名密码认证令牌
            UsernamePasswordAuthenticationToken usernamePassword = new UsernamePasswordAuthenticationToken(loginDTO.getUsername(), loginDTO.getPassword());

            // 对认证令牌进行认证
            Authentication authenticate = authenticationManager.authenticate(usernamePassword);

            // 获取认证后的用户详细信息
            LoginUser login = (LoginUser) authenticate.getPrincipal();
            log.info("当前登录用户信息->{}", login);

            // 登录成功删除验证码
            redisTemplate.delete(CacheConstants.CAPTCHA_KEY + key);

            // 当前登录用户
            User currentUser = login.getUser();

            // 生成token
            Map<String, String> claimMap = new HashMap<>();
            claimMap.put("id", currentUser.getId().toString());

            // 根据用户信息创建JWT Token
            String token = JwtUtils.createToken(claimMap);

            // 将用户信息存入缓存中
            redisTemplate.opsForValue().set(CacheConstants.USER_KEY + token, login, 2, TimeUnit.HOURS);

            // 返回结果
            Map<String, Object> result = new HashMap<>();
            result.put("token", token);
            result.put("nickname", currentUser.getUsername());
            result.put("lastLoginTime", currentUser.getLastLoginTime());

            return result;
        } catch (Exception e) {
            throw new CustomException(ResultCodeEnum.USERNAME_PASSWORD_ERROR);
        }
    }

    @Override
    public Boolean logout() {
        HttpServletRequest request = SecurityHolderUtils.getRequest();
        String token = request.getHeader("token");
        return redisTemplate.delete(CacheConstants.USER_KEY + token);
    }

    @Override
    public User getInfo(String token) {
        // 判断 token 算法为空
        if (token == null) {
            throw new CustomException(ResultCodeEnum.TOKEN_NOT_EXIST);
        }

        // 解析 token
        DecodedJWT decoded = JwtUtils.verifyToken(token);
        Map<String, Claim> claims = decoded.getClaims();
        String userId = claims.get("id").asString();

        // 根据用户ID查询用户信息
        User user = baseMapper.selectById(userId);

        // 设置登录时间
        user.setLastLoginTime(LocalDateTime.now());
        baseMapper.updateById(user);

        return user;
    }

    @Override
    public String captcha(String randomStr) {
        // 定义图形验证码的长和宽
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(100, 40, CommonConstants.CAPTCHA_SIZE, 100);

        // 验证码值
        String code = lineCaptcha.getCode();

        // 将验证码缓存到 redis 中，1 分钟后失效
        redisTemplate.opsForValue().set(CacheConstants.CAPTCHA_KEY + randomStr, code, CommonConstants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);

        // 验证码以base64的格式返回到客户端
        return lineCaptcha.getImageBase64Data();
    }

    @Override
    public User getById(Long id) {
        User user = baseMapper.selectById(id);
        user.setRoles(roleMapper.getRolesByUserId(UserTypeEnum.isAdmin(user.getUsertype()) ? null : user.getId()));
        return user;
    }

    @Override
    public Page<User> getPage(Long current, Long limit, UserQueryDTO userQueryDTO) {
        // 创建 page 对象，传递当前页, 每页记录数
        Page<User> page = new Page<>(current, limit);
        // 条件参数
        String username = userQueryDTO.getUsername();
        String email = userQueryDTO.getEmail();
        String phone = userQueryDTO.getPhone();
        Integer sex = userQueryDTO.getSex();
        Integer status = userQueryDTO.getStatus();

        // 构建条件
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StrUtil.isNotBlank(username), User::getUsername, username);
        queryWrapper.like(StrUtil.isNotBlank(email), User::getEmail, email);
        queryWrapper.like(StrUtil.isNotBlank(phone), User::getPhone, phone);
        queryWrapper.eq(sex != null, User::getSex, sex);
        queryWrapper.eq(status != null, User::getStatus, status);
        queryWrapper.notIn(SecurityHolderUtils.getUserId() != null, User::getId, SecurityHolderUtils.getUserId());

        // 根据修改时间倒序
        queryWrapper.orderByDesc(User::getUpdateTime);

        // 查询
        Page<User> pages = baseMapper.selectPage(page, queryWrapper);

        // 转换
        List<User> users = pages.getRecords().stream()
                .peek(user -> user.setRoles(roleMapper.getRolesByUserId(UserTypeEnum.isAdmin(user.getUsertype()) ? null : user.getId())))
                .toList();
        pages.setRecords(users);

        // 返回结果
        return pages;
    }


    @Override
    public Boolean save(UserSaveDTO userSaveDTO) {
        // 密码加密
        userSaveDTO.setPassword(bCryptPasswordEncoder.encode(userSaveDTO.getPassword()));

        // 保存用户
        User user = UserConverter.INSTANCE.toEntity(userSaveDTO);
        boolean b = super.save(user);

        // 保存用户角色
        userSaveDTO.getRoleIds().forEach(roleId -> userRoleMapper.insert(new UserRole(null, user.getId(), roleId)));

        // 返回结果
        return b;
    }

    @Override
    public Boolean updateById(UserUpdateDTO userUpdateDTO) {
        // 修改用户
        User user = UserConverter.INSTANCE.toEntity(userUpdateDTO);
        boolean b = super.updateById(user);

        // 删除用户绑定的角色
        userRoleMapper.delete(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, user.getId()));

        // 保存用户角色
        userUpdateDTO.getRoleIds().forEach(roleId -> userRoleMapper.insert(new UserRole(null, user.getId(), roleId)));

        // 返回结果
        return b;
    }

    @Override
    public Boolean updatePassword(UserUpdatePwdDTO userUpdatePwdDTO) {
        // 获取用户信息
        User user = SecurityHolderUtils.getUser();

        // 判断密码是否相同
        if (bCryptPasswordEncoder.matches(userUpdatePwdDTO.getOldPassword(), user.getPassword())) {
            // 修改密码
            user.setPassword(bCryptPasswordEncoder.encode(userUpdatePwdDTO.getNewPassword()));

            return super.updateById(user);
        } else {
            throw new CustomException(ResultCodeEnum.OLD_PASSWORD_ERROR);
        }
    }

    @Override
    public Boolean updateProfile(UserProfileDTO userProfileDTO) {
        // 获取当前登录用户
        userProfileDTO.setId(SecurityHolderUtils.getUserId());

        // 修改用户个人信息
        User user = UserConverter.INSTANCE.toEntity(userProfileDTO);
        return super.updateById(user);
    }

    @Override
    public Boolean resetPassword(Long id) {
        //  获取系统用户默认密码
        String defaultPassword = paramService.getParamValueByCode(CommonConstants.DEFAULT_PASSWORD_CODE);

        // 修改用户密码
        User user = new User();
        user.setId(id);
        user.setPassword(bCryptPasswordEncoder.encode(defaultPassword));

        return super.updateById(user);
    }

    @Override
    public Boolean assignRole(AssignRoleDTO assignRoleDTO) {
        // 删除用户角色
        userRoleMapper.delete(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, assignRoleDTO.getUserId()));

        // 判断用户分配的角色是否为空
        if (ObjectUtil.isNotEmpty(assignRoleDTO.getRoleIds())) {
            userRoleMapper.insertBatch(assignRoleDTO);
        }

        return true;
    }

    @Override
    public Boolean updateStatus(Long id, Integer status) {
        User user = new User();
        user.setId(id);
        user.setStatus(status);

        return super.updateById(user);
    }
}




